/*
Gorgeous Ruler js file
author nanzhi
contact nanzhienai@163.com,www.12sui.cn
date 2010.05.15
*/

//var 
var doc = document,
	win = window,
	scr = screen,
	body = doc.body,
	pi = Math.PI,
	sw = scr.availWidth + 70,
	sh = 80,
	//main image
	ruler, 
	//guide
	guide,
	//operate
	operate,
	//set option
	option,
	//is vertical ?
	isVertical = false,
	//is opposite ?
	isOpposite = false,
	//user config
	config = {
		"size" : 60,
		"radius" : 30,
		"space" : 40,
		"lang" : {
			"cn" : {
				"set" : "设置渐变色以及透明度:",
				"start" : "开始:",
				"end" : "结束:",
				"len" : "渐变长度:",
				"lang" : "语言:",
				"btn" : "设置",
				"cancel" : "取消"
			},
			"en" : {
				"set" : "Set the gradient color and alpha:",
				"start" : "Start:",
				"end" : "End:",
				"len" : "Gradient length:",
				"lang" : "Language:",
				"btn" : "Set",
				"cancel" : "Cancel"
			}
		},
		"userkey" : "red|0.8|orange|0.6|50|en"
	};

/**
 * min document.getElementById
 * @method $
 * @param { String } id
 */
function $(el) {
	return typeof el == 'string' ? doc.getElementById(el) : el;
}

/** 
 * delete the extra space 
 * @method trim
 */
String.prototype.trim = function() {
	return this.replace(/(^\s*)|(\s*$)/g, '');
}

/**
 * translate to array
 * @method toArray
 * @param el { Object } arguments || array || html elements
 */
function toArray(el) {
	if(typeof el == 'object' && el.length) {
		var arr = [],
			len = el.length,
			i = 0;	
		for(; i < len; i++) {
			arr.push(el[i]);
		}
		return arr;
	}
}

/**
 * remove html element's attribute
 * @method removeAttribute
 * @param el { HTML element }
 * @param attr { Array || String } attributes
 */
function removeAttribute(el, attr) {
	if(typeof el == 'object' && el.length) {
		while(el[0]) {
			arguments.callee(el[0], attr);
			el.shift();
		}
		return;
	}
	el = $(el);
	if(typeof attr == 'string')	{
		el.removeAttribute(attr);	
	} else if(typeof attr == 'array') {
		var i = 0,
			len = attr.length;
		for(; i < len; i++) {
			el.removeAttribute(attr[len]);
		}
	}
}

/**
 * ovverwrite Object
 * @method mergeCSSJson 
 * @param r { Object }
 * @param s { Object }
 * @return { String }
function mergeCSSJson(r, s) {
	var str = '',
		i,
		j;
	if(s) {
		for(i in s) {
			r[i] = s[i];
		}
	}
	for(j in r) {
		str += j + ':' + r[j] + ';';
	}
	return str;
}
 */

/**
 * add inline css for element
 * @method addCSS
 * @param cssJson { Object } 
 * @param el { Html element } html element || id
 * @attention this method will overwritten the inline styles
function addCSS(cssJson, el) {
	var currentStyle = el.getAttribute('style'),
		len,
		newStyle;

	if(currentStyle != null) {
		currentStyle = currentStyle.trim();
		len = currentStyle.length;
		//translate to json
		if(currentStyle.substring(len - 1) == ';') {
			currentStyle = currentStyle.substring(0, len - 1);
		}
		currentStyle = currentStyle.replace(/\:/g, '":"').replace(/\;/g, '";"');
		currentStyle = '{"' + currentStyle + '"}';
		currentStyle = eval('(' + currentStyle + ')');	

		//merge cssjson
		newStyle = mergeCSSJson(currentStyle, cssJson);
	} else {
		newStyle = mergeCSSJson(cssJson);
	}

	//set new style
	el.setAttribute('style', newStyle);
}
 */

/**
 * add class
 * @method addClass
 * @param el { String || HTML element }
 * @param cname { String }
 */
function addClass(el, cname) {
	el = $(el);
	var old = el.className;
	if(!old) {
		el.className = cname;
	} else {
		el.className = old + ' ' + cname;
	}
}

/**
 * remove class
 * @method removeClass
 * @param el { String || HTML element }
 * @param cname { String }
 */
function removeClass(el, cname) {
	el = $(el);
	var old = el.className,
		arr,
		i,
		len,
		newc = [];
	if(old == cname) {
		removeAttribute(el, 'class');
	} else {
		arr = old.split(' ');	
		len = arr.length;
		for(i = 0; i < len; i++) {
			if(arr[i] != cname)	{
				newc.push(arr[i]);
			}
		}
		if(newc.length == 0) {
			removeAttribute(el, 'class');
		} else {
			el.className = newc.join(' ');
		}
	}
}

/**
 * translate color value to rgb
 * @method toRGB
 * @param color { String } value
 */
function toRGBA(color, alpha) {
	var span = doc.createElement('span'),
		to2,
		r,g,b;
	//get color
	span.style.color = color;
	//opera need append to the body to get color
	body.appendChild(span);
	color = win.getComputedStyle(span, null).getPropertyValue('color');
	body.removeChild(span);
	//translate to rgb
	to2 = parseInt(('0x' + color.slice(1)), 16);
	r = to2 >> 16;
	g = (to2 & 0x00ff00) >> 8;
	b = to2 & 0x0000ff;
	return ('rgba(' + [r, g, b, alpha].join(',') + ')');
}

/**
 * draw the ruler background
 * @method drawRluer
 * @param canvas {html element} the canvas element
 * @param isver {Boolean} is vertical or horizontal
 * @param data {Array} the gradient data,contain start color,end color and length
 * @param size {Array} the width and height
 */
function drawRuler() {
	var cxt,
		data = config.userkey.split('|'),
		s = config.size,
		r = config.radius,
		dia = 2 * r,
		extra = r + config.space,
		i = extra,
		gradient,
		img;

	ruler = doc.createElement('canvas');
	ruler.id = 'r-normal';
	cxt = ruler.getContext('2d');
	
	ruler.width = sw;
	ruler.height = s;
	gradient = cxt.createLinearGradient(0, 0, 0, data[4]);
	gradient.addColorStop(0, toRGBA(data[0], data[1]));
	gradient.addColorStop(1, toRGBA(data[2], data[3]));
	cxt.fillStyle = gradient;

	cxt.beginPath();
	cxt.arc(r, r, r, pi / 2, pi * 3 / 2, false);
	cxt.rect(r, 0, sw, dia);
	cxt.fill();

	for(; i < sw; i++) {
		if(i % 100 == extra) {
			cxt.fillStyle = 'white';
			cxt.font = 'bold 12px tohama';
			cxt.fillText(i - extra, i - (i - extra).toString().length * 3, r + 2);
		}
		cxt.fillStyle = 'black';
		if(i % 10 == 0) {
			cxt.fillRect(i, 0, 1, 12);	
			cxt.fillRect(i, dia - 12, 1, 12);
		} else if(i % 4 == 0) {
			cxt.fillRect(i, 0, 1, 8);	
			cxt.fillRect(i, dia - 8, 1, 8);
		} else if(i % 2 == 0) {
			cxt.fillRect(i, 0, 1, 4);	
			cxt.fillRect(i, dia - 4, 1, 4);
		}

		//reduce loop
		i++;
	}

	cxt.closePath();
	body.appendChild(ruler);
}

/**
 * create guide
 * @method createGuide
 */
function createGuide() {
	var guideNum;
	guide = doc.createElement('span');	
	guide.className = 'guide';
	guideNum = doc.createElement('i');
	guideNum.appendChild(doc.createTextNode('0'));
	guide.appendChild(guideNum);
	body.appendChild(guide);
}

/**
 * create operate
 * @method createOperate
 */
function createOperate() {
	var icon,
	   	j = 0,	
		names = ['o-opposite', 'o-direction', 'o-close', 'o-set'],
		len = names.length,
		frag,
		iname;
	frag = doc.createDocumentFragment();
	operate = doc.createElement('div');
	operate.className = 'operate';
	for(; j < len; j++) {
		icon = doc.createElement('i');
		icon.className = names[j];
		frag.appendChild(icon);
	}
	operate.appendChild(frag);
	body.appendChild(operate);
	
	//加载事件
	operate.addEventListener('click', function(e) {
		icon = e.target;
		iname = icon.className;		
		switch(iname) {
			//opposite
			case names[0]:
				changeDirection('opposite');
				break;
			//direction
			case names[1]:
				changeDirection('direction');
				break;
			//close
			case names[2]:
				win.close();
				break;
			//set
			case names[3]:
				createSetOption();
				break;
		}
	}, false);
}

/**
 * change the direction of the ruler
 * @method changeDirection
 * @param direction { String }
 * @TODO fix the bug when HO (double line when the ruler is too long)
 */
function changeDirection(direction) {
	var str,
		gnum = guide.childNodes[0],
		tran = [sh / 2, sh / 2 - sw],
		dir = {
				"HV" : ['rotate(90deg) scale(1, 1) translate(-' + tran[0] + 'px, -' + tran[0] + 'px)', '0 50%'],//h to v
				"VO" : ['rotate(-90deg) translate(' + tran[1] + 'px, ' + tran[0] + 'px)', '0 50%'],//v to o
				"HO" : ['rotate(0deg) scale(-1, 1) translate(0, 0)', '50% 0']//h to o
			  },
		pnum = sw - 60 + 'px',
		result;
	if(direction == 'direction') {
		if(!isVertical && !isOpposite) {
			//h to v
			str = 'HV';
		} else if(!isVertical && isOpposite) {
			//v to o
			str = 'VO';
		} else if(isVertical && isOpposite) {
			//h to o
			str = 'HO';
		}
		if(isVertical) {
			isVertical = false;
			removeClass(body, 'ver');
			win.resizeTo(sw, sh);
		} else {
			isVertical = true;
			addClass(body, 'ver');
			win.resizeTo(sh, sw);
		}
	} else {
		if(!isVertical && !isOpposite) {
			//h to o
			str = 'HO';
		} else if(isVertical && !isOpposite) {
			//v to o
			str = 'VO';
		} else if(isVertical && isOpposite) {
			//h to v
			str = 'HV';
		}
		isOpposite = isOpposite ? false : true;
	}
	//reset position because it can disappear
	win.moveTo(0, sh);

	//reset
	removeAttribute([guide, gnum, ruler, operate], 'style');
	if(!isVertical && isOpposite) {
		operate.style.left = pnum; 
	} else if(isVertical && isOpposite) {
		operate.style.top = pnum; 
	}

	//set style
	if(str) {
		rotate = dir[str];
		if(rotate[0]) {
			//set transform
			ruler.style.OTransform = rotate[0];
			//for latest version
			ruler.style.transform = rotate[0];
		} 
		if(rotate[1]) {
			//set transformorigin
			ruler.style.OTransformOrigin = rotate[1];
			//for latest version
			ruler.style.transformOrigin = rotate[1];
		}
	}
	//reset guide position
	setGuidePosition();
}

/**
 * set guide position
 * @method setGuidePosition
 * @param e { Event handle }
 */
function setGuidePosition(e) {
	var	inner = guide.childNodes[0],
		extra = config.radius + config.space,
		isUndefined = e === 'undefined' || !e,
		x = !isUndefined ? e.clientX : extra, 
		y = !isUndefined ? e.clientY : extra,
		txt,
		left,
		top,
		mleft,
		mtop;
	if(!isVertical) {
		txt = isOpposite ? (sw - x - extra - 1) : (x - extra);
		if(txt < 0) {
			txt = 0;
			left = isOpposite ? (sw - extra - 1) : extra;
		} else {
			left = x;
		}	
		guide.style.left = left + 'px';
		inner.innerHTML = txt;
		inner.style.marginLeft = '-' + inner.offsetWidth / 2 + 'px';
	} else {
		txt = isOpposite ? (sw - y - extra -1) : (y - extra);
		if(txt < 0) {
			txt = 0;
			top = isOpposite ? (sw - extra -1) : extra;
		} else {
			top = y;
		}
		guide.style.top = top + 'px';
		inner.innerHTML = txt;
		inner.style.marginTop = '-' + inner.offsetHeight / 2 + 'px';
	}
}

/**
 * reset ruler to normal
 * @method resetRuler
 */
function resetRuler() {
	removeAttribute([guide, ruler, guide.childNodes[0], operate], 'style');
	removeAttribute(body, 'class');
	isVertical = false;
	isOpposite = false;
	win.resizeTo(sw, sh);
	win.moveTo(0, sh);
}

/**
 * create set option
 * @method createSetOption
 */
function createSetOption() {
	var target,
		lang = config.lang,
		data = config.userkey.split('|'),
		ltype = data[5],
		uln = lang[ltype],
		inputs,
		sel,
		i = 0,
		len,
		values = [],
		input = function(value) {
					return ('<input size="7" type="text" value="' + value + '" />');
				};

	if(!option) {
		option = doc.createElement('div');	
		option.className = 'option';
		option.innerHTML = uln.set + 
						   uln.start + input(data[0]) + input(data[1]) +
						   uln.end + input(data[2]) + input(data[3]) +
						   uln.len + input(data[4]) +
						   uln.lang + '<select><option value="cn"' + (ltype == 'cn' ? ' selected' : '') + '>中文</option><option value="en"' + (ltype == 'en' ? 'selected' : '') + '>English</option></select>' +
						   '<button id="J_ChangeSure">' + uln.btn + '</button>' +
						   '<button id="J_CancelSet">' + uln.cancel + '</button>';

		option.addEventListener('click', function(e) {
			target = e.target;
			if(target.tagName.toLowerCase() == 'button') {
				if(target.id == 'J_ChangeSure') {
					inputs = toArray(option.getElementsByTagName('input'));
					sel = option.getElementsByTagName('select')[0];
					len = inputs.length;
					for(; i < len; i++) {
						values.push(inputs[i].value);
					}
					values.push(sel.value);
					alert(values.join('|'));
					widget.setPreferenceForKey(values.join('|'), 'style');
				}
				location.reload();
			}	
		}, false);

		body.appendChild(option);
		resetRuler();
		doc.removeEventListener('keypress', attachKey, false);
	}
}

/**
 * key event
 * @method attachKey
 * @param e { Event handle }
 */
function attachKey(e) {
	var kcode = e.keyCode;
	switch (kcode * 1) {
		case 37:
			win.moveBy( - 1, 0);
			break;
		case 38:
			win.moveBy(0, - 1);
			break;
		case 39:
			win.moveBy(1, 0);
			break;
		case 40:
			win.moveBy(0, 1);
			break;
		//'d' change the dirction
		case 100:
			changeDirection('direction');
			break;
		//'o' opposite the ruler
		case 111:
			changeDirection('opposite');
			break;
		//'r' reset
		case 114:
			resetRuler();	
			break;
		//'q' exit
		case 113:
			win.close();
			break;
	}	
}

/**
 * main func,load all event
 */
win.addEventListener("load", function() {
	//set user value	
	widget.preferenceForKey('style') && (config.userkey = widget.preferenceForKey('style'));

	//draw ruler
	drawRuler();

	//create guide 
	createGuide();

	createOperate();

	//init ruler position 
	resetRuler();

	//add move event for document
	doc.addEventListener('mousemove', setGuidePosition, false);

	//move by dir key 
	doc.addEventListener('keypress', attachKey, false);

}, false);
